package github.sf.fw.tools.var;


import github.sf.fw.utils.StrUtil;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class VarInStringResolver {
    private final String str;
    private final List<String> fragments = new ArrayList<>();
    private final List<String> variables = new ArrayList<>();

    private final Pattern operateRegex = Pattern.compile("[+\\-*/]");

    private boolean enableOperate = true;

    private boolean addQuotationMark = true;

    public VarInStringResolver(String str) {
        this.str = str;
        if (StrUtil.isEmpty(str)) {
            throw new IllegalArgumentException("待解析的字符串不能为空");
        }
    }

    public List<String> getFragments() {
        return fragments;
    }

    public List<String> getVariables() {
        return variables;
    }

    public void disableOperate() {
        this.enableOperate = false;
    }

    public void enableOperate() {
        this.enableOperate = true;
    }

    public void setAddQuotationMark(boolean addQuotationMark) {
        this.addQuotationMark = addQuotationMark;
    }

    public void resolve() {
        char[] chars = (" " + str).toCharArray();
        CharStream charStream = new CharStream(chars);
        while (!charStream.eof()) {
            int ch = charStream.next();
            if (ch == '$') {
                handleVariable(charStream);
            } else {
                handleNormal(charStream);
            }
        }
    }

    /**
     * 若想显示变量类的字符串，使用单引号引起来即可
     * 如：'$_{name}'不会被解析为变量name
     * $_{name}会被解析为变量name
     */
    public void resolve0() {
        char[] chars = str.toCharArray();
        int len = chars.length;
        for (int i = 0; i < len; i++) {
            char ch = chars[i];
            if (ch == '$' && i + 2 < len) {
                if (chars[i + 1] == '_' && chars[i + 2] == '{') {
                    i = i + 2;
                    StringBuilder tmp = new StringBuilder();
                    while (i++ < len - 1) {
                        if (chars[i] == '}') {
                            if (tmp.length() > 0) {
                                variables.add(tmp.toString());
                                tmp = new StringBuilder();
                            }
                            break;
                        }
                        tmp.append(chars[i]);
                    }
                    if (tmp.length() > 0) {
                        fragments.add(tmp.toString());
                    }
                }
            } else {
                StringBuilder tmp = new StringBuilder();
                tmp.append(ch);
                boolean escapeClose = ch != '\'';
                StringBuilder escape = new StringBuilder();
                while (i++ < len - 1) {
                    if (chars[i] == '\'') {
                        escapeClose = !escapeClose;
                        if (escapeClose && escape.length() > 0) {
                            String s = escape.toString();
                            if (VarUtil.checkVarName(s, false)) {
                                tmp.append(s);
                            } else {
                                tmp.append("'").append(s).append("'");
                            }
                            escape = new StringBuilder();
                        }
                        continue;
                    } else if (chars[i] == '$' && escapeClose) {
                        i = i - 1;
                        break;
                    }
                    if (!escapeClose) {
                        escape.append(chars[i]);
                        continue;
                    }
                    tmp.append(chars[i]);
                }
                if (tmp.length() > 0) {
                    fragments.add(tmp.toString());
                }
            }
        }
    }


    public String fillVariable(VariablePool variablePool) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < fragments.size(); i++) {
            result.append(fragments.get(i));
            if (i < variables.size()) {
                String varName = variables.get(i);
//                Variable variable = variablePool.getVariable(varName);
                String variableValue = getVariableValue(variablePool, varName);
                if (addQuotationMark) {
                    result.append("'").append(variableValue).append("'");
                } else {
                    result.append(variableValue);
                }
            }
        }
        return result.toString();
    }

    public String toPrepareSql() {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < fragments.size(); i++) {
            result.append(fragments.get(i));
            if (i < variables.size()) {
                String varName = variables.get(i);
                result.append("?");
            }
        }
        return result.toString();
    }

    private void handleNormal(CharStream queue) {
        StringBuilder temp = new StringBuilder();
        while (!queue.eof()) {
            char ch = queue.peek();
            if (ch == '$') {
                break;
            }
            temp.append(ch);
            queue.next();
        }
        if (temp.length() != 0) {
            fragments.add(temp.toString());
        }
    }

    private void handleVariable(CharStream stream) {
        char peek = stream.next();
        if (stream.eof(1)) {
            return;
        }
        char peek1 = stream.peek();
        if (peek == '_' && peek1 == '{') {
            lookUpVariableEnd(stream);
        }
    }

    private void lookUpVariableEnd(CharStream stream) {
        StringBuilder temp = new StringBuilder();
        while (!stream.eof()) {
            stream.next();
            char peek = stream.peek();
            if (peek == '}') {
                variables.add(temp.toString());
                return;
            }
            temp.append(peek);
        }
        if (temp.length() > 0) {
            fragments.add(temp.toString());
        }

    }

    private String getVariableValue(VariablePool variablePool, String varName) {
        if (!enableOperate || !operateRegex.matcher(varName).find()) {
            Variable variable = variablePool.getVariable(varName);
            return variable.getVal();
        }
        String[] varFragments = operateRegex.split(varName);
        List<String> flags = new ArrayList<>();
        Matcher matcher = operateRegex.matcher(varName);
        while (matcher.find()) {
            flags.add(matcher.group());
        }
        //现阶段，暂时只支持字符串和数字
        List<String> realValues = new ArrayList<>(varFragments.length);
        //获取真实值
        for (String varFragment : varFragments) {
            Variable variable = variablePool.getVariable(varFragment);
            if (variable == null) {
                realValues.add(varFragment);
            } else {
                realValues.add(variable.getVal());
            }
        }
        int index = 0;
        Object resultValue = null;
        VarType varType = null;
        for (String realValue : realValues) {
            if (index == 0) {
                if (StrUtil.isNum(realValue)) {
                    varType = VarType._Int;
                } else {
                    varType = VarType._String;
                }
                resultValue = realValue;
            } else {
                String flag = flags.get(index - 1);
                switch (varType) {
                    case _Int:
                        if (StrUtil.isNum(realValue)) {
                            Object finalResultValue = resultValue;
                            if (String.class.isAssignableFrom(resultValue.getClass())) {
                                resultValue = arithmeticOperation(flag, () -> new BigDecimal((String) finalResultValue), realValue);
                            } else if (BigDecimal.class.isAssignableFrom(resultValue.getClass())) {
                                resultValue = arithmeticOperation(flag, () -> (BigDecimal) finalResultValue, realValue);
                            }
                        } else {
                            varType = VarType._String;
                            resultValue = strAdd(flag, resultValue, realValue);
                        }
                        break;
                    case _String:
                        resultValue = strAdd(flag, resultValue, realValue);
                        break;
                    default:
                        break;
                }
            }
            index++;
        }
        return String.valueOf(resultValue);
    }

    private String strAdd(String flag, Object lastResult, String nextValue) {
        if ("+".equals(flag)) {
            return lastResult + nextValue;
        }
        throw new RuntimeException(String.format("字符串只支持+操作符,操作符%s错误", flag));
    }

    private Object arithmeticOperation(String flag, Supplier<BigDecimal> lastResult, String nextValue) {
        switch (flag) {
            case "+":
                return lastResult.get().add(new BigDecimal(nextValue));
            case "-":
                return lastResult.get().subtract(new BigDecimal(nextValue));
            case "*":
                return lastResult.get().multiply(new BigDecimal(nextValue));
            case "/":
                return lastResult.get().divide(new BigDecimal(nextValue), 2, RoundingMode.CEILING);
            default:
                break;
        }
        return null;
    }

    static class CharStream {
        int index;
        char[] chars;

        int len;

        public CharStream(char[] chars) {
            this.chars = chars;
            this.len = chars.length;
        }

        public boolean eof() {
            return index > len - 1;
        }

        public boolean eof(int n) {
            return index + n >= len;
        }

        public char next() {
            if (!eof()) {
                return chars[index++];
            }
            throw new IllegalStateException(StrUtil.format("index {} 超过 len {}", index, len));
        }

        public char peek() {
            return chars[index];
        }

        public char peek(int n) {
            return chars[index + n];
        }
    }
}
